﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using System.Xml.Serialization;
using NDatabase.Api;

namespace NDatabase.Repository
{
    /// <summary>
    /// NDatabase Context Helper
    /// </summary>
    public class NDatabaseContextHelper
    {
        /// <summary>
        /// Initializes the specified context.
        /// </summary>
        /// <param name="context">The context.</param>
        public static void Initialize(INDatabaseContext context)
        {
            var contextType = context.GetType();

            var props = contextType.GetProperties();
            var sets = props.Where(x => x.PropertyType.Name.Contains("NDBSet`")).ToList();

            RegisterDBSet(context, sets);
        }

        /// <summary>
        /// Registers the database set.
        /// </summary>
        /// <param name="context">The context.</param>
        /// <param name="propertyInfos">The property infos.</param>
        private static void RegisterDBSet(INDatabaseContext context, List<PropertyInfo> propertyInfos)
        {
            foreach (var propertyInfo in propertyInfos)
            {
                var pt = propertyInfo.PropertyType;
                var obj = Activator.CreateInstance(pt);

                var method = pt.GetMethod("Register");
                method.Invoke(obj, new object[] { context });

                propertyInfo.SetValue(context, obj);

                InitializeIndexs(context, obj);
            }
        }

        /// <summary>
        /// Initializes the indexs.
        /// </summary>
        /// <param name="context">The context.</param>
        /// <param name="dbSet">The database set.</param>
        private static void InitializeIndexs(INDatabaseContext context, object dbSet)
        {
            var type = dbSet.GetType().GetGenericArguments().FirstOrDefault();
            if (type != null)
            {
                var props = type.GetProperties();
                foreach (var propertyInfo in props)
                {
                    var attrs = propertyInfo.GetCustomAttributes(false);
                    if (attrs.Length <= 0)
                        continue;
                    if (attrs[0] is Attributes.NDBIndexAttribute)
                    {
                        AddIndexOn(context.DataBase, propertyInfo, type);
                    }

                    if (attrs[0] is Attributes.NDBUniqueIndexAttribute)
                    {
                        AddUniqueIndexOn(context.DataBase, propertyInfo, type);
                    }
                }
            }
        }

        /// <summary>
        /// Adds the index on.
        /// </summary>
        /// <param name="odb">The odb.</param>
        /// <param name="propertyInfo">The property information.</param>
        /// <param name="genericType">Type of the generic.</param>
        private static void AddIndexOn(IOdb odb, PropertyInfo propertyInfo, Type genericType)
        {
            var indexManager = GetIndexManager(odb, genericType);
            var indexName = string.Format("{0}Index", propertyInfo.Name);

            if (ExistIndex(indexManager, indexName)) return;

            var addIndexOnMethod = indexManager.GetType().GetMethod("AddIndexOn");

            addIndexOnMethod.Invoke(indexManager, new object[] { indexName, new[] { propertyInfo.Name } });
        }

        /// <summary>
        /// Adds the unique index on.
        /// </summary>
        /// <param name="odb">The odb.</param>
        /// <param name="propertyInfo">The property information.</param>
        /// <param name="genericType">Type of the generic.</param>
        private static void AddUniqueIndexOn(IOdb odb, PropertyInfo propertyInfo, Type genericType)
        {
            var indexManager = GetIndexManager(odb, genericType);
            var indexName = string.Format("{0}Index", propertyInfo.Name);
            if (ExistIndex(indexManager, indexName)) return;

            var addIndexOnMethod = indexManager.GetType().GetMethod("AddUniqueIndexOn");

            addIndexOnMethod.Invoke(indexManager, new object[] { indexName, new[] { propertyInfo.Name } });
        }

        /// <summary>
        /// Gets the index manager.
        /// </summary>
        /// <param name="odb">The odb.</param>
        /// <param name="genericType">Type of the generic.</param>
        /// <returns></returns>
        private static object GetIndexManager(IOdb odb,Type genericType)
        {
            var cType = odb.GetType();
            var methodInfo = cType.GetMethod("IndexManagerFor");
            var indexManagerForMethod = methodInfo.MakeGenericMethod(genericType);
            return indexManagerForMethod.Invoke(odb, null);
        }

        /// <summary>
        /// Exists the index.
        /// </summary>
        /// <param name="indexManager">The index manager.</param>
        /// <param name="indexName">Name of the index.</param>
        /// <returns></returns>
        private static bool ExistIndex(object indexManager, string indexName)
        {
            var existIndexMethod = indexManager.GetType().GetMethod("ExistIndex");
            var result = existIndexMethod.Invoke(indexManager, new object[] {indexName});
            return (bool) result;
        }
    }
}
